<?php

namespace PFinal;

/**
 * 类自动加载器
 *
 * $loader = new \PFinal\ClassLoader();
 * $loader->add('', __DIR__ . '/src');
 * $loader->add('PFinal\\', __DIR__ . '/framework');
 * $loader->register();
 *
 * @author  Zou Yiliang
 * @since   1.0
 */
class ClassLoader
{
    private $prefixDirsPsr4 = array();
    private $fallbackDirsPsr4 = array();

    public function addPsr4($prefix, $paths, $prepend = false)
    {
        if (!$prefix) {
            if ($prepend) {
                $this->fallbackDirsPsr4 = array_merge((array)$paths, $this->fallbackDirsPsr4);
            } else {
                $this->fallbackDirsPsr4 = array_merge($this->fallbackDirsPsr4, (array)$paths);
            }
            return;
        }

        $length = strlen($prefix);
        if ('\\' !== $prefix[$length - 1]) {
            throw new \InvalidArgumentException('A non-empty PSR-4 prefix must end with a namespace separator.');
        }
        if (!array_key_exists($prefix, $this->prefixDirsPsr4)) {
            $this->prefixDirsPsr4[$prefix] = (array)$paths;
            return;
        }
        if ($prepend) {
            $this->prefixDirsPsr4[$prefix] = array_merge((array)$paths, $this->prefixDirsPsr4[$prefix]);
        } else {
            $this->prefixDirsPsr4[$prefix] = array_merge($this->prefixDirsPsr4[$prefix], (array)$paths);
        }
    }

    /**
     * 注册自动加载器到 PHP SPL autoloader
     *
     * @param bool $prepend
     */
    public function register($prepend = false)
    {
        spl_autoload_register(array($this, 'autoload'), true, $prepend);
    }

    /**
     * @param string $class 完整类名
     */
    public function autoload($class)
    {
        if ($file = $this->findFile($class)) {
            include $file;
        }
    }

    private function findFile($class, $ext = '.php')
    {
        // 兼容 PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
        if ('\\' == $class[0]) {
            $class = substr($class, 1);
        }

        // 将类名转换为路径
        $logicalPath = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;

        foreach ($this->prefixDirsPsr4 as $prefix => $dirs) {
            if (0 === strpos($class, $prefix)) {
                $length = strlen($prefix);
                foreach ($dirs as $dir) {
                    $file = $dir . DIRECTORY_SEPARATOR . substr($logicalPath, $length);
                    if (file_exists($file)) {
                        return $file;
                    }
                }
                break;
            }
        }

        foreach ($this->fallbackDirsPsr4 as $dir) {
            $file = $dir . DIRECTORY_SEPARATOR . $logicalPath;
            if (file_exists($file)) {
                return $file;
            }
        }
    }
}
